﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.OleDb;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using TestOnlineProject.Models.DataAccess;
using TestOnlineProject.Common;
using TestOnlineProject.Models.Entities;
using Match = System.Text.RegularExpressions.Match;

namespace TestOnlineProject.Models.BusinessLogic
{
    public class UserBLO
    {
        UserDAO userDAO = new UserDAO();
        ParticipantDAO participantDao = new ParticipantDAO();
        SectionDAO sectionDAO = new SectionDAO();
        FeedbackDAO feedbackDAO = new FeedbackDAO();
        ReportDAO violationDAO = new ReportDAO();
        DiscussionDAO discussionDAO = new DiscussionDAO();
        UserTestDAO userTestDAO = new UserTestDAO();
        TestDAO testDAO = new TestDAO();
        QuestionDAO questionDAO = new QuestionDAO();
        AccountBLO accountBLO = new AccountBLO();

        /// <summary>
        /// Select user list.
        /// </summary>
        /// <param name="noList"></param>
        /// <param name="page">Number of page</param>
        /// <param name="keyWord">Search key word</param>
        /// <param name="column">Column name</param>
        /// <param name="sort">Sort type</param>
        /// <returns>User list</returns>

        public List<User> ToList(ref List<NoModel> noList, int? page, string keyWord = "", string column = "", string sort = "")
        {
            // User list.
            var users = userDAO.Select();

            // Search by key word.
            if (keyWord.Equals(""))
            {
                users = users.OrderBy(u => u.UserId).ToList();
            }
            else
            {
                users = users.Where(u => (u.FullName.ToLower().Contains(keyWord.ToLower()) || u.Username.ToLower().Contains(keyWord.ToLower()))).OrderBy(u => u.UserId).ToList();
            }

            // Sort by sort type and solumn name.
            switch (column + sort)
            {
                case "NoAsc":
                    users = users.OrderBy(u => u.UserId).ToList();
                    break;
                case "NoDesc":
                    users = users.OrderByDescending(u => u.UserId).ToList();
                    break;
                case "UsernameAsc":
                    users = users.OrderBy(u => u.Username).ToList();
                    break;
                case "UsernameDesc":
                    users = users.OrderByDescending(u => u.Username).ToList();
                    break;
                case "FullNameAsc":
                    users = users.OrderBy(u => u.FullName).ToList();
                    break;
                case "FullNameDesc":
                    users = users.OrderByDescending(u => u.FullName).ToList();
                    break;
                case "EmailAsc":
                    users = users.OrderBy(u => u.Email).ToList();
                    break;
                case "EmailDesc":
                    users = users.OrderByDescending(u => u.Email).ToList();
                    break;
                case "RoleAsc":
                    users = users.OrderBy(u => u.RoleId).ToList();
                    break;
                case "RoleDesc":
                    users = users.OrderByDescending(u => u.RoleId).ToList();
                    break;
                case "JoinedTimeAsc":
                    users = users.OrderBy(u => u.JoinedTime).ToList();
                    break;
                case "JoinedTimeDesc":
                    users = users.OrderByDescending(u => u.JoinedTime).ToList();
                    break;
                case "IsActivatedAsc":
                    users = users.OrderBy(u => u.IsActivated).ToList();
                    break;
                case "IsActivatedDesc":
                    users = users.OrderByDescending(u => u.IsActivated).ToList();
                    break;
            }

            // Generate no. List.
            foreach (var u in users)
            {
                noList.Add(new NoModel { Id = u.UserId });
            }
            noList = noList.OrderBy(n => n.Id).ToList();
            for (int i = 0; i < noList.Count; i++)
            {
                noList[i].No = i + 1;
            }

            return users;
        }

        public List<User> ToList()
        {
            var users = userDAO.Select();
            return users;
        }

        /// <summary>
        /// Add user.
        /// </summary>
        /// <param name="user">User object</param>
        /// <returns>Number of user has bee added</returns>

        public int Add(User user)
        {
            try
            {
                user.Username = user.Username.ToLower();
                user.Password = accountBLO.GenerateMD5(user.Password);
                user.JoinedTime = DateTime.Now;
                user.LastChangedTime = DateTime.Now;
                user.IsActivated = true;

                if (CheckDuplicateUsername(user.Username))
                {
                    // Duplicate username.
                    return -1;
                }

                if (CheckDuplicateEmail(user.Email))
                {
                    // Duplicate email.
                    return -2;
                }

                // Add user.
                int result = userDAO.Insert(user);

                return result;
            }
            catch (Exception)
            {
                return 0;
            }
        }

        /// <summary>
        /// Find user by id.
        /// </summary>
        /// <param name="id">User id</param>
        /// <returns>User object</returns>

        public User Find(int id)
        {
            // Select user by id.
            var user = userDAO.SelectByUserId(id);

            return user;
        }

        /// <summary>
        /// Check duplicate username.
        /// </summary>
        /// <param name="username">Username</param>
        /// <param name="userId">Id of User</param>
        /// <returns>Duplicate username or email or not</returns>

        private bool CheckDuplicateUsername(string username, int userId = 0)
        {
            // Select user.
            var user = userDAO.SelectByUsername(username);

            // Check user.
            if (user != null && user.UserId != userId)
            {
                return true;
            }
            return false;
        }

        private bool CheckDuplicateEmail(string email, int userId = 0)
        {
            // Select user.
            var user = userDAO.SelectByEmail(email);

            // Check user.
            if (user != null && user.UserId != userId)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Check participant.
        /// </summary>
        /// <param name="userId">User ID</param>
        /// <param name="subjectId">Subject ID</param>
        /// <returns>Participant is existent or not</returns>

        public bool CheckParticipant(int userId, int subjectId)
        {
            // Select user.
            var participant = userDAO.SelectByUserIdSubjectId(userId, subjectId);

            // Check participant.
            if (participant != null)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Deactivate users.
        /// </summary>
        /// <param name="ids">User ids string</param>
        /// <returns>Number of user has been deactivated</returns>

        public int Deactivate(string ids = "")
        {
            // Split id string to int array.
            var spliter = new StringHelper();
            var idInt = spliter.Split(ids);

            var result = 0;
            // Deactivate users.
            foreach (var id in idInt)
            {
                // Select user by id.
                var user = userDAO.SelectByUserId(id);

                // Not Admin.
                if (user.RoleId != 1)
                {
                    // Check user is activated?
                    if (user.IsActivated)
                    {
                        user.IsActivated = false;
                        result += userDAO.Update(user);
                    }
                    else
                    {
                        result += 1;
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Activate users.
        /// </summary>
        /// <param name="ids">User ids string</param>
        /// <returns>Number of user has been activated</returns>

        public int Activate(string ids = "")
        {
            // Split id string to int array.
            var spliter = new StringHelper();
            var idInt = spliter.Split(ids);

            var result = 0;
            // Activate users.
            foreach (var id in idInt)
            {
                // Select user by id.
                var user = userDAO.SelectByUserId(id);

                // Not Admin.
                if (user.RoleId != 1)
                {
                    // Check user is inactivated?
                    if (!user.IsActivated)
                    {
                        user.IsActivated = true;
                        result += userDAO.Update(user);
                    }
                    else
                    {
                        result += 1;
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Modify user.
        /// </summary>
        /// <param name="user">User object</param>
        /// <returns>Number of user has been modify</returns>

        public int Modify(User user)
        {
            try
            {
                // Select user by id.
                var us = userDAO.SelectByUserId(user.UserId);

                if (!user.LastChangedTime.ToString().Equals(us.LastChangedTime.ToString()))
                {
                    // Has some one change before
                    return -1;
                }

                if (CheckDuplicateEmail(user.Email, user.UserId))
                {
                    // Duplicate email.
                    return -3;
                }

                // Modify user.
                us.Username = user.Username;
                if (!us.Password.Equals(user.Password))
                {
                    us.Password = accountBLO.GenerateMD5(user.Password);
                }
                else
                {
                    us.Password = us.Password;
                }
                us.Email = user.Email;
                us.RoleId = user.RoleId;
                us.FullName = user.FullName;
                us.LastChangedTime = DateTime.Now;
                us.ImageUrl = user.ImageUrl;

                var result = userDAO.Update(user);
                return result;
            }
            catch (Exception)
            {
                return 0;
            }
        }

        /// <summary>
        /// Remove user.
        /// </summary>
        /// <param name="ids"></param>
        /// <returns></returns>

        public int Remove(string ids = "")
        {
            try
            {
                var spliter = new StringHelper();

                // Split id string to int array.
                var idInt = spliter.Split(ids);
                var result = 0;

                foreach (var id in idInt)
                {
                    var user = userDAO.SelectByUserId(id);
                    if (user != null)
                    {
                        //// Delete Participant
                        //participantDao.Delete(id);

                        //// Delete Section
                        //sectionDAO.DeleteByUserId(id);

                        //// Delete Discussion
                        //discussionDAO.DeleteByUserId(id);

                        //// Delete Feedback
                        //feedbackDAO.DeleteByUserId(id);

                        //// Delete Violation
                        //violationDAO.DeleteByUserId(id);

                        //// Delete UserTest
                        //userTestDAO.DeleteByUserId(id);

                        //// Delete Test
                        //testDAO.DeleteByUserId(id);

                        //// Delete Question
                        //questionDAO.DeleteByUserId(id);

                        //// Delete User
                        result += userDAO.DeleteByUserId(id);
                    }
                }
                return result;
            }
            catch (Exception)
            {
                return 0;
            }
        }

        public User Find(string email)
        {
            try
            {
                var user = userDAO.SelectByEmail(email);
                return user;
            }
            catch (Exception)
            {
                return null;
            }
        }
        /// <summary>
        /// search user
        /// </summary>
        /// <param name="searchText"></param>
        /// <param name="subjectId"></param>
        /// <returns>list of user</returns>

        public IEnumerable SearchUser(string searchText, int subjectId)
        {
            var users =
                userDAO.Select().Where(u => u.Username.Contains(searchText) || u.FullName.Contains(searchText)).ToList();
            var listuser = users.Where(user => participantDao.SelectByUserIdSubjectId(user.UserId, subjectId) == null)
                .Select(user => new { user.UserId, user.FullName, user.Email, user.Username }).Distinct();
            return listuser;
        }

        /// <summary>
        /// import users
        /// </summary>
        /// <param name="conection"></param>
        /// <param name="filepath"></param>
        /// <returns> number of users were imported</returns>

        public Tuple<List<UserErrorModel>, int, int, bool, bool> ParseFile(string conection, string filepath)
        {
            var listUser = new List<UserErrorModel>();
            bool fatalError = false;
            int countSuccess = 0;
            int countFail = 0;
            var time = DateTime.Now.AddSeconds(90);
            bool timeout = false;

            try
            {
                // Fill the dataset
                var adapter = new OleDbDataAdapter("SELECT * FROM [Sheet1$]", conection);
                var ds = new DataSet();
                adapter.Fill(ds, "result");
                DataTable data = ds.Tables["result"];

                // check exist columns
                if (!data.Columns.Contains("Username"))
                {
                    fatalError = true;
                }
                else if (!data.Columns.Contains("FullName"))
                {
                    fatalError = true;
                }
                else if (!data.Columns.Contains("Role"))
                {
                    fatalError = true;
                }
                else if (!data.Columns.Contains("Email"))
                {
                    fatalError = true;
                }
                else if (!data.Columns.Contains("Password"))
                {
                    fatalError = true;
                }
                else
                {
                    for (int i = 0; i <= data.Rows.Count - 1; i++)
                    {
                        // check timeout
                        if(DateTime.Now >time)
                        {
                            timeout = true;
                            break;
                        }
                        // get data from excel
                        var userError = new UserErrorModel();
                        userError.Username = data.Rows[i].Field<string>("Username");
                        userError.Password = data.Rows[i].Field<string>("Password");
                        userError.FullName = data.Rows[i].Field<string>("FullName");
                        userError.Email = data.Rows[i].Field<string>("Email");
                        userError.Role = data.Rows[i].Field<string>("Role");

                        // Regular expression
                        const string usernameValid = @"^[0-9A-Za-z]+$";
                        const string emailValid = @"^(\w+[\.\-])*\w+@(\w+\.)+[A-Za-z]+$";
                        Match match = null;
                        Match matchEmail = null;
                        if (!String.IsNullOrEmpty(userError.Username))
                        {
                            match = Regex.Match(userError.Username, usernameValid);
                        }
                        if (!String.IsNullOrEmpty(userError.Email))
                        {
                            matchEmail = Regex.Match(userError.Email, emailValid);
                        }
                        // check value in column

                        if (String.IsNullOrEmpty(userError.Username))
                        {
                            countFail++;
                            userError.Error = "Username is null or empty!!";
                            listUser.Add(userError);
                        }
                        else if (String.IsNullOrEmpty(userError.Password))
                        {
                            countFail++;
                            userError.Error = "Password is null or empty!!!";
                            listUser.Add(userError);
                        }
                        else if (String.IsNullOrEmpty(userError.FullName))
                        {
                            countFail++;
                            userError.Error = "FullName is null or empty!!!";
                            listUser.Add(userError);
                        }
                        else if (String.IsNullOrEmpty(userError.Email))
                        {
                            countFail++;
                            userError.Error = "Email is null or empty!!!";
                            listUser.Add(userError);
                        }
                        else if (String.IsNullOrEmpty(userError.Role))
                        {
                            countFail++;
                            userError.Error = "Role is null or empty!!!";
                            listUser.Add(userError);
                        }
                        else if (userDAO.SelectByUsername(userError.Username) != null)
                        {
                            countFail++;
                            userError.Error = "This username is duplicated!!";
                            listUser.Add(userError);
                        }
                        else if (userDAO.SelectByEmail(userError.Email) != null)
                        {
                            countFail++;
                            userError.Error = "This email is duplicated!!";
                            listUser.Add(userError);
                        }
                        else if (userError.Username.Contains(" "))
                        {
                            countFail++;
                            userError.Error = "Username can't contain whitspace!!";
                            listUser.Add(userError);
                        }
                        else if (matchEmail != null && !matchEmail.Success)
                        {
                            countFail++;
                            userError.Error = "Email must look like: dienvm@yahoo.com";
                            listUser.Add(userError);
                        }
                        else if (userError.Username.Length > 20 || userError.Username.Length < 4)
                        {
                            countFail++;
                            userError.Error = "Username has lenghth is 4 - 20 characters!!";
                            listUser.Add(userError);
                        }
                        else if (userError.Password.Length < 6 || userError.Password.Length > 50)
                        {
                            countFail++;
                            userError.Error = "Password has lenghth is 6 - 50 characters!!";
                            listUser.Add(userError);
                        }
                        else if (userError.FullName.Length > 50 || userError.FullName.Length < 5)
                        {
                            countFail++;
                            userError.Error = "FullName has length is 5 - 50 characters!!";
                            listUser.Add(userError);
                        }
                        else if (userError.Email.Length > 50)
                        {
                            countFail++;
                            userError.Error = "Email must less than 50 characters!!";
                            listUser.Add(userError);
                        }
                        else if (match != null && !match.Success)
                        {
                            countFail++;
                            userError.Error = "Username just contain letters and numbers!!";
                            listUser.Add(userError);
                        }

                        else if (!userError.Role.ToLower().Equals("student") &&
                                 !userError.Role.ToLower().Equals("teacher"))
                        {
                            countFail++;
                            userError.Error = "Role must be Student or Teacher!!";
                            listUser.Add(userError);
                        }
                        else
                        {
                            // add value to object
                            User user = new User();

                            if (userError.Role.ToLower().Equals("student"))
                            {
                                user.RoleId = 3;
                            }
                            else if (userError.Role.ToLower().Equals("teacher"))
                            {
                                user.RoleId = 2;
                            }
                            user.Username = data.Rows[i].Field<string>("Username").ToLower();
                            user.Password = accountBLO.GenerateMD5(data.Rows[i].Field<string>("Password"));
                            user.FullName = data.Rows[i].Field<string>("FullName");
                            user.Email = data.Rows[i].Field<string>("Email");
                            user.JoinedTime = DateTime.Now;
                            user.LastChangedTime = DateTime.Now;
                            user.IsActivated = true;
                            int result = userDAO.Insert(user);
                            if (result == 0)
                            {
                                countFail++;
                                userError.Error = "Can't import this user!";
                                listUser.Add(userError);
                            }
                            else
                            {
                                listUser.Add(userError);
                                countSuccess++;
                            }
                        }
                    }
                }
                File.Delete(filepath);
                return new Tuple<List<UserErrorModel>, int, int, bool, bool>(listUser, countSuccess, countFail, fatalError, timeout);
            }
            catch (Exception)
            {
                File.Delete(filepath);
                return new Tuple<List<UserErrorModel>, int, int, bool, bool>(null, 0, 0, true, false);
            }

        }
    }
}